package com.eureka.bclass.util;

import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description: RSA非对称加密
 * @Author: lixing lixing_java@163.com
 * @Date: 2020/4/3 14:27
 * <p>
 * 对称加密的加、解密使用相同密钥，安全性不高，容易被破解
 * <p>
 * RSA非对称加密的加、解密使用不同密钥，公钥PK是公开的，私钥SK是保密的，加密算法E和解密算法D也都是公开的。
 *  公钥加密的信息，只有私钥才能解密，反之，私钥加密的信息，只有公钥才能解密，那么只要私钥不泄漏，通信就是安全的
 */
public class RSAUtil {
	private static final Logger logger = LoggerFactory.getLogger(RSAUtil.class);
	public static final String KEY_ALGORITHM = "RSA";
	public static final String SIGNATURE_ALGORITHM = "SHA1WithRSA";
	private static final int MAX_ENCRYPT_BLOCK = 117;
	private static final int MAX_DECRYPT_BLOCK = 128;
	// 私钥
	public static final String PRIVATE_KEY = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAIySBwUlhQjNcNtq1lXsOZXaM2p10OTndV5KR+7YgG0Yg8XYDHkrPh3XmhiR66PpMGlna2WtmM7N1ehs7krBbB2h1SFolA+C0gH7oG6Ijb4hsTrOebUGidnHwIG5sdopLoPU4Y1RI9OxujmRNb50ktLulecnAEp7bOt8kdIKNdDxAgMBAAECgYBRKkwVY6miAIWGm/SwXhqN9RS1BtwcH608SWWpeqcE1x/XD718BUTzlDbssuy9kvqaEdYIOqk918oIT0V0MqTKxxsfwH/K4xCaJGqqv6VkjMqMnApNPLnTZ06mccbGC4MMVuM5Lqqilahx4M75QOeuOBq2pQY2oIW6AKEgvYNxQQJBANIvO0edkGiQqodfVDIq3tIqbNAd26kK4HMbZ8MtsCqdraqDPaiENeafwDl+JaVFoukDJcVTstg5zpQA0YCe8SkCQQCrNixoXYbwBGEkRXAvYJ5Bd8pDErBazZPMY0BKmUyw38lw0R2CZtI92M7OGEHG5k8i2nDL/wjofiDLx6KLfvKJAkBTSRXzVdMwLAr2o2aojmZG3zrI6NvuyvmPgw+Fvqfs6xgy1dhr2x/EqA6yeXMPX12jeXTREU5v8dASw5RApDqpAkBSLU5jSYDa8z8tCacvFlDfW3/Zcb7z1nh6Qak6JRqKSt+FJtnjHTcMOGQKx9nKfgC92dsKDv9XmDJlnQyHqAcBAkAUtVgF0B6lNUPg4zPOJCAAGhcIeiiuuV3WlMRJDsKqJtZwnXSQy1cO1rHBCxzhvQsVWZ8+CqhXng5qUWhKpJ2J";
	// 公钥
	public static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCMkgcFJYUIzXDbatZV7DmV2jNqddDk53VeSkfu2IBtGIPF2Ax5Kz4d15oYkeuj6TBpZ2tlrZjOzdXobO5KwWwdodUhaJQPgtIB+6BuiI2+IbE6znm1BonZx8CBubHaKS6D1OGNUSPTsbo5kTW+dJLS7pXnJwBKe2zrfJHSCjXQ8QIDAQAB";

	public RSAUtil() {}

	public static void main(String[] args) throws Exception {
		Map<String, Object> map = RSAUtil.genKeyPair();
		String publicKey = RSAUtil.getPublicKey(map);
		String privateKey = RSAUtil.getPrivateKey(map);
		System.out.println("私钥--");
		System.out.println(privateKey);
		System.out.println("公钥--");
		System.out.println(publicKey);

		String content = "superAdmin";

        // 私钥加密字符串
        String a = RSABase64URLEncodeByPriK(PRIVATE_KEY, content);
        System.out.println(a);

        // 公钥解密字符串
        String b = URLDecodeBase64RSAByPubK(PUBLIC_KEY, a);
        System.out.println(b);

        // 公钥加密字符串
        String c = RSABase64URLEncodeByPubK(PUBLIC_KEY, b);
        System.out.println(c);

        // 私钥解密字符串
		String d = URLDecodeBase64RSAByPriK(PRIVATE_KEY, c);
		System.out.println(d);
	}

	public static Map<String, Object> genKeyPair() throws Exception {
		KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
		keyPairGen.initialize(1024);
		KeyPair keyPair = keyPairGen.generateKeyPair();
		RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
		RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
		Map<String, Object> keyMap = new HashMap(2);
		keyMap.put("RSAPublicKey", publicKey);
		keyMap.put("RSAPrivateKey", privateKey);
		return keyMap;
	}

	public static String sign(byte[] data, String privateKey) throws Exception {
		byte[] keyBytes = Base64.decode(privateKey);
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initSign(privateK);
		signature.update(data);
		return Base64.encode(signature.sign());
	}

	public static String sign(byte[] data, PrivateKey privateKey) throws Exception {
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initSign(privateKey);
		signature.update(data);
		return Base64.encode(signature.sign());
	}

	public static PrivateKey fileToPrivateKey(String privateKeyFile) {
		File key = new File(privateKeyFile);
		try {
			FileInputStream is = new FileInputStream(key);
			FileChannel channel = is.getChannel();
			byte[] buffer = new byte[is.available()];
			channel.read(ByteBuffer.wrap(buffer));
			byte[] keyBytes = Base64.decode(new String(buffer));
			PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
			KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
			PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
			return privateK;
		} catch (Throwable var9) {
			logger.error("fileToPrivateKey catch Throwable", var9);
			return null;
		}
	}

	public static PublicKey fileToPublicKey(String publicKeyFile) {
		logger.info("publicKeyFile path is " + publicKeyFile);
		File cert = new File(publicKeyFile);

		try {
			FileInputStream is = new FileInputStream(cert);
			FileChannel channel = is.getChannel();
			byte[] buffer = new byte[is.available()];
			channel.read(ByteBuffer.wrap(buffer));
			byte[] keyBytes = Base64.decode(new String(buffer));
			X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
			KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
			PublicKey publicK = keyFactory.generatePublic(keySpec);
			return publicK;
		} catch (Throwable var9) {
			logger.error("fileToPublicKey catch Throwable", var9);
			return null;
		}
	}

	public static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
		byte[] keyBytes = Base64.decode(publicKey);
		X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		PublicKey publicK = keyFactory.generatePublic(keySpec);
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initVerify(publicK);
		signature.update(data);
		return signature.verify(Base64.decode(sign));
	}

	public static boolean verify(byte[] data, PublicKey publicKey, String sign) throws Exception {
		Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
		signature.initVerify(publicKey);
		signature.update(data);
		return signature.verify(Base64.decode(sign));
	}

	public static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
		byte[] keyBytes = Base64.decode(privateKey);
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(2, privateK);
		int inputLen = encryptedData.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;

		for (int i = 0; inputLen - offSet > 0; offSet = i * MAX_DECRYPT_BLOCK) {
			byte[] cache;
			if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
				cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
			} else {
				cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
			}

			out.write(cache, 0, cache.length);
			++i;
		}

		byte[] decryptedData = out.toByteArray();
		out.close();
		return decryptedData;
	}

	public static byte[] decryptByPublicKey(byte[] encryptedData, String publicKey) throws Exception {
		byte[] keyBytes = Base64.decode(publicKey);
		X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key publicK = keyFactory.generatePublic(x509KeySpec);
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(2, publicK);
		int inputLen = encryptedData.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;

		for (int i = 0; inputLen - offSet > 0; offSet = i * MAX_DECRYPT_BLOCK) {
			byte[] cache;
			if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
				cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
			} else {
				cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
			}

			out.write(cache, 0, cache.length);
			++i;
		}

		byte[] decryptedData = out.toByteArray();
		out.close();
		return decryptedData;
	}

	public static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
		byte[] keyBytes = Base64.decode(publicKey);
		X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key publicK = keyFactory.generatePublic(x509KeySpec);
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(1, publicK);
		int inputLen = data.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;

		for (int i = 0; inputLen - offSet > 0; offSet = i * MAX_ENCRYPT_BLOCK) {
			byte[] cache;
			if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
				cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
			} else {
				cache = cipher.doFinal(data, offSet, inputLen - offSet);
			}

			out.write(cache, 0, cache.length);
			++i;
		}

		byte[] encryptedData = out.toByteArray();
		out.close();
		return encryptedData;
	}

	public static byte[] encryptByPrivateKey(byte[] data, String privateKey) throws Exception {
		byte[] keyBytes = Base64.decode(privateKey);
		PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
		KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
		Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
		Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
		cipher.init(1, privateK);
		int inputLen = data.length;
		ByteArrayOutputStream out = new ByteArrayOutputStream();
		int offSet = 0;

		for (int i = 0; inputLen - offSet > 0; offSet = i * MAX_ENCRYPT_BLOCK) {
			byte[] cache;
			if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
				cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
			} else {
				cache = cipher.doFinal(data, offSet, inputLen - offSet);
			}

			out.write(cache, 0, cache.length);
			++i;
		}

		byte[] encryptedData = out.toByteArray();
		out.close();
		return encryptedData;
	}

	public static String getPrivateKey(Map<String, Object> keyMap) throws Exception {
		Key key = (Key) keyMap.get("RSAPrivateKey");
		return Base64.encode(key.getEncoded());
	}

	public static String getPublicKey(Map<String, Object> keyMap) throws Exception {
		Key key = (Key) keyMap.get("RSAPublicKey");
		return Base64.encode(key.getEncoded());
	}

	/**
	 * 私钥加密
	 * @param privateKey 私钥
	 * @param content 加密内容
	 * @return
	 * @throws Exception
	 */
	public static String RSABase64URLEncodeByPriK(final String privateKey,String content) throws Exception {
		byte[] bytes = RSAUtil.encryptByPrivateKey(content.getBytes(), privateKey);
		String str = Base64.encode(bytes);
		str = URLEncoder.encode(str, "UTF-8");
		return str;
	}

	/**
	 * 公钥解密
	 * @param publicKey 公钥
	 * @param content 解密内容
	 * @return
	 * @throws Exception
	 */
	public static String URLDecodeBase64RSAByPubK(final String publicKey,String content) throws Exception {
		String str = URLDecoder.decode(content, "UTF-8");
		byte[] bytes = RSAUtil.decryptByPublicKey(Base64.decode(str), publicKey);
		return new String(bytes);
	}

	/**
	 * 公钥加密
	 * @param publicKey 公钥
	 * @param content 加密内容
	 * @return
	 * @throws Exception
	 */
	public static String RSABase64URLEncodeByPubK(final String publicKey,String content) throws Exception {
		byte[] bytes = RSAUtil.encryptByPublicKey(content.getBytes(StandardCharsets.UTF_8), publicKey);
		String str = Base64.encode(bytes);
		str = URLEncoder.encode(str, "UTF-8");
		return str;
	}

	/**
	 * 私钥解密
	 * @param privateKey 私钥
	 * @param content 解密内容
	 * @return
	 * @throws Exception
	 */
	public static String URLDecodeBase64RSAByPriK(final String privateKey,String content) throws Exception {
		String str = URLDecoder.decode(content, "UTF-8");
		byte[] bytes = RSAUtil.decryptByPrivateKey(Base64.decode(str), privateKey);
		return new String(bytes, StandardCharsets.UTF_8);
	}
}
